<script>
  import {
    isVisible,
    firstVisibleElementIndex,
    scrollIntoViewIfNeeded
} from '../../_utils/scrollIntoView.js'
  import {
    addShortcutFallback,
    removeShortcutFallback,
    onKeyDownInShortcutScope
} from '../../_utils/shortcuts.js'
  import { smoothScroll } from '../../_utils/smoothScroll.js'
  import { getScrollContainer } from '../../_utils/scrollContainer.js'
  import { store } from '../../_store/store.js'
  import { emit } from '../../_utils/eventBus.js'

  const VISIBILITY_CHECK_DELAY_MS = 600

  const keyToElement = key => document.getElementById(key)
  const elementToKey = element => element.getAttribute('id')
  const scope = 'global'

  const shouldIgnoreEvent = event => {
    // For accessibility reasons, do not override the arrowup/arrowdown behavior for radio buttons
    // (e.g. in a poll). Up/down is supposed to change the radio value, not the current status.
    const { target, key } = event
    const isRadio = target &&
    target.tagName === 'INPUT' &&
    target.type === 'radio'
    const isArrow = key === 'ArrowUp' || key === 'ArrowDown'
    return isRadio && isArrow
  }

  export default {
    data: () => ({
      activeItemChangeTime: 0,
      elements: document.getElementsByClassName('shortcut-list-item'),
      spoilersShown: false
    }),
    store: () => store,
    oncreate () {
      addShortcutFallback(scope, this)
    },
    ondestroy () {
      removeShortcutFallback(scope, this)
    },
    methods: {
      onKeyDown (event) {
        if (shouldIgnoreEvent(event)) {
          return
        }
        if (event.key === 'z' && this.store.get().currentTimeline.startsWith('status/')) {
          // if we're in a thread, toggle all content warnings on or off
          event.stopPropagation()
          event.preventDefault()
          const { spoilersShown } = this.get()
          emit('toggleAllSpoilers', !spoilersShown)
          this.set({ spoilersShown: !spoilersShown })
          return
        }
        if (event.key === 'j' || event.key === 'ArrowDown') {
          event.stopPropagation()
          event.preventDefault()
          this.changeActiveItem(1, event.timeStamp)
          return
        }
        if (event.key === 'k' || event.key === 'ArrowUp') {
          event.stopPropagation()
          event.preventDefault()
          this.changeActiveItem(-1, event.timeStamp)
          return
        }
        let activeItemKey = this.checkActiveItem(event.timeStamp)
        if (!activeItemKey) {
          const { elements } = this.get()
          const index = firstVisibleElementIndex(elements).first
          if (index >= 0) {
            activeItemKey = elementToKey(elements[index])
          }
        }
        if (activeItemKey) {
          onKeyDownInShortcutScope(activeItemKey, event)
        }
      },
      changeActiveItem (movement, timeStamp) {
        const { elements } = this.get()
        let index = -1
        let activeItemKey = this.checkActiveItem(timeStamp)
        if (activeItemKey) {
          const len = elements.length
          let i = -1
          while (++i < len) {
            if (elementToKey(elements[i]) === activeItemKey) {
              index = i
              break
            }
          }
        }
        if (index === 0 && movement === -1) {
          activeItemKey = null
          this.set({ activeItemKey })
          smoothScroll(getScrollContainer(), 0, /* horizontal */ false, /* preferFast */ false)
          return
        }
        if (index === -1) {
          const { first, firstComplete } = firstVisibleElementIndex(elements)
          index = (movement > 0) ? firstComplete : first
        } else {
          index += movement
        }
        if (index >= 0 && index < elements.length) {
          activeItemKey = elementToKey(elements[index])
          this.setActiveItem(activeItemKey, timeStamp)
          scrollIntoViewIfNeeded(keyToElement(activeItemKey))
        }
      },
      checkActiveItem (timeStamp) {
        const activeElement = document.activeElement
        if (!activeElement) {
          return null
        }
        // The user might be focused on an element inside a toot. We want to
        // move relative to that toot.
        const activeArticle = activeElement.closest('article')
        if (!activeArticle) {
          return null
        }
        const activeItem = activeArticle.getAttribute('id')
        if (!activeItem) {
          return null
        }
        const { activeItemChangeTime } = this.get()
        if ((timeStamp - activeItemChangeTime) > VISIBILITY_CHECK_DELAY_MS &&
            !isVisible(keyToElement(activeItem))) {
          this.setActiveItem(null, 0)
          return null
        }
        return activeItem
      },
      setActiveItem (key, timeStamp) {
        this.set({ activeItemChangeTime: timeStamp })
        try {
          keyToElement(key).focus({
            preventScroll: true
          })
        } catch (err) {
          console.error('Ignored focus error', err)
        }
      }
    }
  }
</script>
